package scalab.core.utils

import scalab.utils.Reporter

import scalab.core.gates.{Input, Output}

object AlgorithmFactory extends Reporter{

	override val name: String = "AlgorithmFactory"

	import scala.collection.immutable.{Set, Map, HashSet, HashMap}
	import scalab.core.component.{Universe, Component, SinkComponent, ExecutableComponent}

	/*private[core] def createUpdate(sources: Iterable[Buildable]): 
	(Set[ExecutableComponent] => Pair[Boolean, Set[ExecutableComponent]]) = {fresh: Set[ExecutableComponent] =>
		var newFresh = fresh
		(sources forall {e =>
			//check for fresh here? not that useful...
			val (toDate, moreFresh) = e update newFresh
			if(toDate)
				newFresh = newFresh ++ moreFresh
			toDate
		}, newFresh)
	}*/

	private[core] def createUpdate(providers: Iterable[Provider]): 
	(
		(Set[Component], Set[(Output, Input)], Set[ExecutableComponent]) 
		=> 
		(Boolean, Set[Component], Set[(Output, Input)], Set[ExecutableComponent])
	) = {(visited: Set[Component], cycles: Set[(Output, Input)], fresh: Set[ExecutableComponent]) =>
		var newVisited = visited
		var newCycles = cycles
		var newFresh = fresh
		(providers forall {p =>
			//check for fresh here? not that useful...
			val (toDate, moreVisited, moreCycles, moreFresh) = p.update(newVisited, newCycles, newFresh)
			if(toDate)
				newFresh = newFresh ++ moreFresh
			newVisited = newVisited ++ moreVisited
			newCycles = newCycles ++ moreCycles
			toDate
		}, newVisited, newCycles, newFresh)
	}

	/*private[core] def createUpdate(caller: ExecutableComponent, sources: Iterable[Buildable]): 
	(Set[ExecutableComponent] => Pair[Boolean, Set[ExecutableComponent]]) = {fresh: Set[ExecutableComponent] =>
		if(fresh contains caller){
			//error("fresh found!")
			(true, fresh)
		}
		else{
			//error(fresh)
			var newFresh = fresh
			((sources forall {s: Buildable =>
				//check for fresh here? not that useful...
				val (toDate, moreFresh) = s update newFresh
				if(toDate)
					newFresh = newFresh ++ moreFresh
				toDate
			}) && caller.exec, newFresh + caller) //change lazy && to bitwise & for keep going mode...
		}	
	}*/

	private[core] def createUpdate(caller: ExecutableComponent): 
	(
		(Set[Component], Set[(Output, Input)], Set[ExecutableComponent]) 
		=> 
		(Boolean, Set[Component], Set[(Output, Input)], Set[ExecutableComponent])
	) = {(visited: Set[Component], cycles: Set[(Output, Input)], fresh: Set[ExecutableComponent]) =>
		if(fresh contains caller){
			//error("fresh found!")
			(true, visited, cycles, fresh) //carefull! do not return true if keepgoing mode is inserted...
		}
		else{
			//error(fresh)
			var newVisited = visited + caller
			var newCycles = cycles
			var newFresh = fresh
			((caller.inputs forall {i: Input => i.providers forall {p =>
				//check for fresh here? not that useful...
				if(!cycles.contains((p.output, i))){

					if(visited contains p) //we are going to follow a cycle
						newCycles = newCycles + Pair(p.output, i)

					val (toDate, moreVisited, moreCycles, moreFresh) = p.update(newVisited, newCycles, newFresh)
					if(toDate)
						newFresh = newFresh ++ moreFresh
					newVisited = newVisited ++ moreVisited
					newCycles = newCycles ++ moreCycles
					toDate
				}
				else
					true
			}}) && caller.exec, newVisited, newCycles, newFresh + caller) //change lazy && to bitwise & for keep going mode...
				//also change newFresh + caller in keep going mode...
		}	
	}

	//providers => Iterable[]
	private[core] def createValidate(providers: Iterable[Provider]): 
	(
		(Set[Component], Set[(Output, Input)], Set[SinkComponent]) 
		=> 
		(Boolean, Set[Component], Set[(Output, Input)], Set[SinkComponent])
	) = {(visited: Set[Component], cycles: Set[(Output, Input)], validated: Set[SinkComponent]) =>
		var newVisited = visited
		var newCycles = cycles
		var newValidated = validated
		(providers forall {p =>
			//check for fresh here? not that useful...
			val (valid, moreVisited, moreCycles, moreValidated) = p.validate(newVisited, newCycles, newValidated)
			if(valid)
				newValidated = newValidated ++ moreValidated
			newVisited = newVisited ++ moreVisited
			newCycles = newCycles ++ moreCycles
			valid
		}, newVisited, newCycles, newValidated)
	}

	private[core] def createValidate(caller: SinkComponent): 
	(
		(Set[Component], Set[(Output, Input)], Set[SinkComponent]) 
		=> 
		(Boolean, Set[Component], Set[(Output, Input)], Set[SinkComponent])
	) = {(visited: Set[Component], cycles: Set[(Output, Input)], validated: Set[SinkComponent]) =>
		if(validated contains caller){
			//error("fresh found!")
			(true, visited, cycles, validated) //carefull! do not return true if keepgoing mode is inserted...
		}
		else{
			//error(fresh)
			var newVisited = visited + caller
			var newCycles = cycles
			var newValidated = validated
			(caller.inputs forall {i: Input => i.providers forall {p =>
				//check for fresh here? not that useful...
				if(cycles.contains((p.output, i)))
					true
				else{

					if(visited contains p) //we are going to follow a cycle
						newCycles = newCycles + Pair(p.output, i)

					val (valid, moreVisited, moreCycles, moreValidated) = p.validate(newVisited, newCycles, newValidated)
					if(valid)
						newValidated = newValidated ++ moreValidated
					newVisited = newVisited ++ moreVisited
					newCycles = newCycles ++ moreCycles
					valid
				}
			}}, newVisited, newCycles, newValidated + caller)
				//also change newValidated + caller in keep going mode...
		}	
	}

	/*private[core] def createValidate(providers: Iterable[Buildable]): 
	(
		(Set[Component], Set[(Output, Input)], Set[ExecutableComponent]) 
		=> 
		(Boolean, Set[Component], Set[(Output, Input)], Set[ExecutableComponent])
	) = {(visited: Set[Component], cycles: Set[(Output, Input)], fresh: Set[ExecutableComponent]) =>
		var newVisited = visited
		var newCycles = cycles
		var newFresh = fresh
		(sources forall {s =>
			//check for fresh here? not that useful...
			val (toDate, moreVisited, moreCycles, moreFresh) = s.update(newVisited, newCycles, newFresh)
			if(toDate)
				newFresh = newFresh ++ moreFresh
			newVisited = newVisited ++ moreVisited
			newCycles = newCycles ++ moreCycles
			toDate
		}, newVisited, newCycles, newFresh)
	}*/

	private[core] def createUpToDate(providers: Iterable[Provider]): 
	(
		(Set[Resource], Set[Component], Set[(Output, Input)], Set[ExecutableComponent]) 
		=> 
		(Boolean, Set[Component], Set[(Output, Input)], Set[ExecutableComponent])
	) = {(products: Set[Resource], visited: Set[Component], cycles: Set[(Output, Input)], fresh: Set[ExecutableComponent]) =>
		var newVisited = visited
		var newCycles = cycles
		var newFresh = fresh
		(providers forall {p =>
			//check for fresh here? not that useful...
			val (toDate, moreVisited, moreCycles, moreFresh) = p.upToDate(products, newVisited, newCycles, newFresh)
			if(toDate)
				newFresh = newFresh ++ moreFresh
			newVisited = newVisited ++ moreVisited
			newCycles = newCycles ++ moreCycles
			toDate
		}, newVisited, newCycles, newFresh)
	}
	
	private[core] def createUpToDate(caller: ExecutableComponent): 
	(
		(Set[Resource], Set[Component], Set[(Output, Input)], Set[ExecutableComponent]) 
		=> 
		(Boolean, Set[Component], Set[(Output, Input)], Set[ExecutableComponent])
	) = {(products: Set[Resource], visited: Set[Component], cycles: Set[(Output, Input)], fresh: Set[ExecutableComponent]) =>
		if(fresh contains caller){
			//error("fresh found!")
			(true, visited, cycles, fresh) //carefull! do not return true if keepgoing mode is inserted...
		}
		else{
			//error(fresh)
			var newVisited = visited + caller
			var newCycles = cycles
			var newFresh = fresh
			((caller.inputs forall {i: Input => 
				val srcs = i.sourceSet map {d => d.getResource}
				i.providers forall {p =>
					//check for fresh here? not that useful...
					if(!cycles.contains((p.output, i))){

						if(visited contains p) //we are going to follow a cycle
							newCycles = newCycles + Pair(p.output, i)

						val (toDate, moreVisited, moreCycles, moreFresh) = p.upToDate(srcs, newVisited, newCycles, newFresh)
						if(toDate)
							newFresh = newFresh ++ moreFresh
						newVisited = newVisited ++ moreVisited
						newCycles = newCycles ++ moreCycles
						toDate
					}
					else
						true
				}
			}) && caller.locallyUpToDate(products), newVisited, newCycles, newFresh + caller) //change lazy && to bitwise & for keep going mode...
				//also change newFresh + caller in keep going mode...
		}	
	}

	/*private[core] def createComponentsDeps(component: Component): 
	(
		(Set[Component], Set[(Output, Input)], Set[CustomComponent]) 
		=> 
		(Set[Component], Set[(Output, Input)], Map[Component, Set[Component]])
	) = {(visited: Set[Component], cycles: Set[(Output, Input)], deps: Map[Component, Set[Component]]) =>
		var newVisited = visited
		var newCycles = cycles
		var newDeps = deps
		(components.providers foreach {p =>
			//check for fresh here? not that useful...
			val (moreVisited, moreCycles, moreDeps) = createComponentDeps(p)(newVisited, newCycles, newDeps)
			if(valid)
				newValidated = newValidated ++ moreValidated
			newVisited = newVisited ++ moreVisited
			newCycles = newCycles ++ moreCycles
			valid
		}
		(newVisited, newCycles, newDeps)
	}*/

	private[core] def createComponentsDeps(caller: Component): 
	(
		(Set[Component], Set[(Output, Input)], Map[Component, Set[Component]]) 
		=> 
		(Set[Component], Set[(Output, Input)], Map[Component, Set[Component]])
	) = {(visited: Set[Component], cycles: Set[(Output, Input)], deps: Map[Component, Set[Component]]) =>
		if(deps contains caller){
			//error("fresh found!")
			(visited, cycles, deps) //carefull! do not return true if keepgoing mode is inserted...
		}
		else{
			//error(fresh)
			var newVisited = visited + caller
			var newCycles = cycles
			var newDeps = deps.update(caller, deps.getOrElse(caller, HashSet.empty[Component] + caller))
			if(caller != Universe){
				caller.asInstanceOf[SinkComponent].inputs foreach {i: Input => i.providers foreach {p =>

					//check for fresh here? not that useful...
					if(!cycles.contains((p.output, i))){

						if(visited contains p) //we are going to follow a cycle
							newCycles = newCycles + Pair(p.output, i)

						val (moreVisited, moreCycles, moreDeps) = createComponentsDeps(p)(newVisited, newCycles, newDeps)
						newDeps = 
							newDeps.update(caller, newDeps.getOrElse(caller, HashSet.empty[Component]) ++ 
							moreDeps.getOrElse(caller, HashSet.empty[Component]))
						newVisited = newVisited ++ moreVisited
						newCycles = newCycles ++ moreCycles
					}
				}}
			}
			(newVisited, newCycles, newDeps)
		}	
	}

	private[core] def createComponentsDeps(components: Iterable[Component]): 
		Map[Component, Set[Component]] = {
		var newVisited: Set[Component] = HashSet.empty[Component]
		var newCycles: Set[(Output, Input)] = HashSet.empty[(Output, Input)]
		var newDeps: Map[Component, Set[Component]] = HashMap.empty[Component, Set[Component]]
		components foreach {c =>
			val (moreVisited, moreCycles, moreDeps) = createComponentsDeps(c)(newVisited, newCycles, newDeps)
				newDeps = 
					newDeps.update(c, newDeps.getOrElse(c, HashSet.empty[Component]) ++ 
					moreDeps.getOrElse(c, HashSet.empty[Component]))
				newVisited = newVisited ++ moreVisited
				newCycles = newCycles ++ moreCycles
		}
		newDeps
	}

	
}
